#ifdef HAVE_CONFIG_H
#include<config.h>
#endif

#ifdef HAVE_SYS_TYPES_H
#include<sys/types.h>
#endif

#ifdef HAVE_STRING_H
#include<string.h>
#endif

#ifdef HAVE_SYS_SOCKET_H 
#include<sys/socket.h>
#endif

#ifdef HAVE_NETINET_IN_H
#include<netinet/in.h>
#endif

#ifdef HAVE_ARPA_INET_H
#include<arpa/inet.h>
#endif


#include <ysip2.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <time.h>

#define YSIP_DELETE(s) if((s) != NULL) {delete (s); (s) = NULL;}
#define YSIP_GET_REGION_INFO(pos) (YSIP2_REGION_INFO *)((char*) gDb->region_info + sizeof(YSIP2_REGION_INFO)  * (pos))
#define YSIP_GET_REGION_DETAIL(pos) (char *)((char*) gDb->detail_info + (pos))
const char * gMagic = "ysip";

using namespace std;

typedef struct _YSIP_DB {
	char magic[4]; // magic , just make sure the file is ysip db format.
	int32_t size; // file size
	int32_t ip_info_size; // ip info record count.
	int32_t region_info_size; // region info record count
#ifdef YSIP2_IP_DATA_ALL
	int32_t detail_info_size;
#endif
	time_t region_mtime; // region.txt modtime
	time_t ip_mtime; // ip.txt modtime
#ifdef YSIP2_IP_DATA_ALL
	time_t detail_mtime; //detail.txt modtime
#endif
	void * ip_info; // ip info pointer.
	void * region_info; // region info pointer.
#ifdef YSIP2_IP_DATA_ALL
	void * detail_info;
#endif
	void * mmap; // mmap pointer
} YSIP_DB;

YSIP_DB * gDb = NULL;
bool initing = false;

template <class T>
bool ysipLoadData(const string file, T * array, int &count);


template <class T, class S>
int ysipBinarySearch(S s, void* begin, int32_t size, T * t) {
	int32_t low = 0, high = size, middle = 0;

	T * x;
	while(low <= high) {
		middle = (low + high) / 2;
		x = (T*) ((char*)begin + middle * sizeof(T));
		if(*x == s) {
			memcpy(t, x, sizeof(T));
			return middle;
		} else if(*x > s) {
			high = middle - 1;
		} else {
			low = middle + 1;	
		}
	}
	return -1;
}
template <class T>
int32_t ysipLoadData(const string file, YSIP_DB * ydh,int fd,off_t offset) {
	int count = 0;
	ssize_t size = 0;
	ifstream infile;
	char buffer[1024];
	infile.open(file.c_str(), ifstream::in);
	if(infile.is_open()) {
		lseek(fd, offset, SEEK_SET);
		T t;
		while(infile.getline(buffer, 1024)) {
			t = buffer;
			size += write(fd, (void*) &t, sizeof(T));
			count ++;
		}
		infile.close();
		return count;
	} else {
		cerr << "YSIP2: Open " << file << " error !" << endl;
		return false;
	}
}

bool ysipReadDb(const string file, const string root) {
	if(gDb == NULL) {
		YSIP_DB ydh;
		struct stat buffer;

		errno = 0;
		//获取文件的基本信息
		if(stat(file.c_str(), &buffer) != 0) {
			if(errno != ENOENT) {
				cerr << "YSIP2: stat file : " << file << " error,errno=" << errno << endl; 
			}
			return false;
		}

		//如果文件的大小比其头部应该的大小还小
		if(buffer.st_size < (off_t)sizeof(YSIP_DB)) {
			cerr << "YSIP2: file: " << file << " size to small" << endl;
			return false;
		}

		//尝试打开文件
		errno = 0;
		int fd = open(file.c_str(), O_RDONLY | O_EXCL);
		if(fd < 0) {
			cerr << "YSIP2: Open " << file << " error, errno=" << errno << endl;
			return false;		
		}

		//尝试读取文件头
		errno = 0;
		ssize_t len = read(fd, (void*) &ydh, sizeof(YSIP_DB));
		if(len != sizeof(YSIP_DB)) {
			close(fd);
			cerr << "YSIP2: Open " << file << " succ, but read header failed, errno=" << errno << endl;
			return false;
		}

		//检测文件头部的magic是否正确
		if(strncmp(ydh.magic , gMagic, strlen(gMagic)) != 0) {
			close(fd);
			cerr << "YSIP2: Give file " << file << " format is not valid!" << endl;
			return false;
		}

		//检测文件大小是否正确
		if(buffer.st_size != ydh.size) {
			close(fd);
			cerr << "YSIP2: Give header info is not valid header:"<<ydh.size <<" ysip.db:" << buffer.st_size << endl;
			return false;
		}

		//检测IP数据以及Region数据是否存在
		if(ydh.ip_info_size < 1 || ydh.region_info_size < 1) {
			close(fd);
			cerr << "YSIP2: Give ip info size is 0" << endl;
			return false;
		}
#ifdef YSIP2_IP_DATA_ALL 

#endif
		//检测IP/Region是否为最新
		string ip_file = root + "/var/ysip/ip.txt";
		string region_file = root + "/var/ysip/region.txt";
		if(stat(ip_file.c_str(), &buffer) == 0) {
			if(buffer.st_mtime > ydh.ip_mtime) {
				close(fd);
				DEBUG4("YSIP2: ip.txt file modtime is newer,ysip.db:", ydh.ip_mtime," ip.txt:", buffer.st_mtime);
				return false;
			}
		}
		if(stat(region_file.c_str(), &buffer) == 0) {
			if(buffer.st_mtime > ydh.region_mtime) {
				close(fd);
				DEBUG4("YSIP2: region.txt file modtime is newer,ysip.db:", ydh.region_mtime," region.txt:", buffer.st_mtime);
				return false;
			}
		}

#ifdef YSIP2_IP_DATA_ALL 
		string detail_file = root + "/var/ysip/detail.txt";
		if(stat(detail_file.c_str(), &buffer) == 0) {
			if(buffer.st_mtime > ydh.detail_mtime) {
				close(fd);
				DEBUG4("YSIP2: detail.txt file modtime is newer,ysip.db:", ydh.detail_mtime," detail.txt:", buffer.st_mtime);
				return false;
			}
		}
#endif
		gDb = new YSIP_DB;
		memcpy(gDb, &ydh, sizeof(YSIP_DB));

		errno = 0;
		gDb->mmap = mmap(NULL, gDb->size, PROT_READ, MAP_SHARED, fd, 0);
		if(gDb->mmap == MAP_FAILED) {
			close(fd);
			cerr << "YSIP2: map failed , please check the memory is enough,errno" << errno <<endl;
			YSIP_DELETE(gDb);
			return false;
		}
		close(fd);
		gDb->region_info = (char*)(gDb->mmap) + sizeof(YSIP_DB);
		gDb->ip_info = (char*)(gDb->region_info) + gDb->region_info_size * sizeof(YSIP2_REGION_INFO);
#ifdef YSIP2_IP_DATA_ALL
		gDb->detail_info = (char*)(gDb->ip_info) + gDb->ip_info_size * sizeof(YSIP2_IP_INFO);
#endif
	}
	return true;
}


bool ysipInitDb(const string root) {
	if(initing) {
		return true;
	}
	YSIP_DB  ydh;
	bzero(&ydh, sizeof(YSIP_DB));
	string db_file = root + "/var/ysip/ysip.db";
	mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
	//不管文件是否存在都尝试去unlink
	unlink(db_file.c_str());
	errno = 0;
	int fd = open(db_file.c_str(),O_TRUNC | O_CREAT | O_EXCL | O_WRONLY, mode);
	if(fd > 0) {
		off_t offset = sizeof(YSIP_DB);
		struct stat ys;
		string region_file = root + "/var/ysip/region.txt";
		bzero(&ys, sizeof(struct stat));
		errno = 0;
		if(stat(region_file.c_str(), &ys) == 0) {
			ydh.region_mtime = ys.st_mtime;	
			ydh.region_info_size = ysipLoadData<YSIP2_REGION_INFO>(region_file, &ydh, fd, offset);
			DEBUG2("YSIP2:DBG:read region_info_size=", ydh.region_info_size);
		} else {
			close(fd);
			cerr << "YSIP2: " << region_file << " does not exists, errno=" << errno << endl;
			initing = false;
			return false;	
		}

		offset += sizeof(YSIP2_REGION_INFO) * ydh.region_info_size;
		
		string ip_file = root + "/var/ysip/ip.txt";
		bzero(&ys, sizeof(struct stat));
		if(stat(ip_file.c_str(), &ys) == 0){
			ydh.ip_mtime = ys.st_mtime;
			ydh.ip_info_size = ysipLoadData<YSIP2_IP_INFO>(ip_file, &ydh, fd, offset);
			DEBUG2("YSIP2:DBG:read ip_info_size=", ydh.ip_info_size);
		} else {
			close(fd);
			cerr << "YSIP2: " << ip_file << " does not exists,errno=" << errno <<endl;
			initing = false;
			return false;
		}

		offset += sizeof(YSIP2_IP_INFO) * ydh.ip_info_size;
#ifdef YSIP2_IP_DATA_ALL
		string detail_file = root + "/var/ysip/detail.txt";
		bzero(&ys, sizeof(struct stat)) ;
		errno = 0;
		if(stat(detail_file.c_str(), &ys) == 0) {
			ydh.detail_mtime = ys.st_mtime;
			ifstream infile;
			char buffer[1024];
			int count = 0;
			infile.open(detail_file.c_str(),ifstream::in);
			if(infile.is_open()) {
				lseek(fd, offset, SEEK_SET);
				while(infile.good()) {
					infile.getline(buffer, 1024);
					offset += write(fd, (void*) buffer, strlen(buffer) + 1);
					count ++;
				}
				ydh.detail_info_size = count;

				infile.close();
			} else {
				cerr << "YSIP2: Open " << detail_file << " error " << endl;
			}
		} else{
			close(fd);
			cerr << "YSIP2: " << detail_file << " doest not exists, errno=" << errno << endl;
		}

#endif

		memcpy(ydh.magic, gMagic, strlen(gMagic));
		ydh.size = offset;

		offset = lseek(fd, 0, SEEK_SET);
		offset = write(fd, (void*)& ydh, sizeof(YSIP_DB));

		close(fd);
		if(ysipReadDb(db_file, root)) {
			initing = false;
			return true;
		}
	} 
	cerr << "YSIP2: create file " << db_file << " error, errno=" << errno <<endl;
	initing = false;
	return false;
}


bool ysipInit(const string root) {
	if(gDb == NULL) {
		string realroot = root.size() > 0 ? root : YSIP_ROOT;
		string db_file = realroot + "/var/ysip/ysip.db";
		if(ysipReadDb(db_file, realroot)) {
			return true;
		}
		if(!ysipInitDb(realroot)) {
			return false;
		}
	}
	return true;
}

bool ysipGetIpInfo(const string ip, YSIP2_REGION_INFO & regionInfo){
	unsigned int uip = htonl(inet_addr(ip.c_str()));		
	YSIP2_IP_INFO ipInfo;
	if(ysipBinarySearch(uip, gDb->ip_info, gDb->ip_info_size, &ipInfo) != -1) {
		if(ysipBinarySearch(ipInfo.region_id, gDb->region_info, gDb->region_info_size, &regionInfo) != -1) {
			return true;
		}
	}

	return false;
}

#ifdef YSIP2_IP_DATA_ALL
bool ysipGetIpInfo(const string ip, YSIP2_REGION_INFO_EX & regionInfo) {
	unsigned int uip = htonl(inet_addr(ip.c_str()));		
	YSIP2_IP_INFO ipInfo;
	if(ysipBinarySearch(uip, gDb->ip_info, gDb->ip_info_size, &ipInfo) != -1) {
		if(ysipBinarySearch(ipInfo.region_id, gDb->region_info, gDb->region_info_size, (YSIP2_REGION_INFO *)&regionInfo) != -1) {
			regionInfo.detail = YSIP_GET_REGION_DETAIL(ipInfo.position);
			return true;
		}
	}
	return false;
}
#endif

bool ysipGetRegionInfo(const int64_t id, YSIP2_REGION_INFO &regionInfo) {
	return ysipBinarySearch(id, gDb->region_info, gDb->region_info_size, &regionInfo) != -1;
}

bool ysipGetRegionChilds(const int64_t id, vector<YSIP2_REGION_INFO> &childs, bool includeSelf){
	YSIP2_REGION_INFO regionInfo;
	YSIP2_REGION_INFO * ri;
	// 1000000000 's level is 0, if level is 0 , we will not optimizer.
	// other's we will only walk through the xx0000 due to the data is so, just a trick.

	//make childs is empty
	childs.clear();
#ifdef YSIP2_X_FORMAT
	if(id > 1000000000) {
		int ret = ysipBinarySearch(id, gDb->region_info, gDb->region_info_size, &regionInfo);
		if(ret > -1) {
			if(includeSelf) {
				childs.push_back(regionInfo);
			}	

			while(++ret < gDb->region_info_size) {
				ri = YSIP_GET_REGION_INFO(ret);
				if(ri->id / 100000000 != regionInfo.id / 100000000) {
					break;
				}
				if(ri->parent_id == id) {
					childs.push_back(*ri);
				}
			}
			return true;
		} else {
			return false;
		}
	//如果 id == 86 则表明为中国
	} else if(id == 86) {
		static vector<YSIP2_REGION_INFO> china;

		if(ysipGetRegionInfo(id, regionInfo)) {
			if(includeSelf) {
				childs.insert(childs.begin(), regionInfo);
			}
			if(china.size() < 1) {
				for(int i = 0; i < gDb->region_info_size; i ++) {
					ri = YSIP_GET_REGION_INFO(i);				
					if(ri->parent_id == id) {
						china.push_back(*ri);
					}
				}
			}
		}
		childs = china;
		return true;
	}
#else
	if(ysipGetRegionInfo(id, regionInfo)) {
		if(includeSelf) {
			childs.push_back(regionInfo);
		}

		for(int i = 0; i < gDb->region_info_size; i ++) {
			ri = YSIP_GET_REGION_INFO(i);				
			if(ri->parent_id == id) {
				childs.push_back(*ri);
			}
		}
		return true;
	}
#endif
	return false;
}

bool ysipGetRegionParent(const int64_t id, YSIP2_REGION_INFO &regionInfo) {
	if(ysipGetRegionInfo(id, regionInfo)) {
		if(regionInfo.parent_pos != -1) {
			memcpy((void*)&regionInfo, YSIP_GET_REGION_INFO(regionInfo.parent_pos), sizeof(YSIP2_REGION_INFO)); 
			return true;
		}
	}
	return false;
}

bool ysipGetRegionParents(const int64_t id, vector<YSIP2_REGION_INFO> & parents, bool includeSelf, int level) {
	YSIP2_REGION_INFO regionInfo;
	if(ysipGetRegionInfo(id, regionInfo)) {
		if(includeSelf) {
			parents.push_back(regionInfo);	
		}
		if(level > YSIP_MAX_LEVEL || level < YSIP_MIN_LEVEL) {
			level = YSIP_MIN_LEVEL;
		}
		while(regionInfo.parent_pos > -1) {
			regionInfo = *YSIP_GET_REGION_INFO(regionInfo.parent_pos);
			if(regionInfo.level >= level) {
				parents.push_back(regionInfo);
			} else {
				break;
			}
		}
		return true;
	}
	return false;
}

void ysipDestory(){
	if(gDb->mmap != NULL) {
		munmap(gDb->mmap, gDb->size);
	}
	YSIP_DELETE(gDb);
}
int ysipGetStat(int type) {
	return 0; 
}

